﻿using System;
using System.Activities;
using System.Activities.Statements;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Globalization;

namespace Common.Activities
{
    public delegate void UserStepSequenceChangedEventHandler(object sender, UserStepSequenceChangedEventArgs e);
    public class UserStepSequenceChangedEventArgs : EventArgs
    {
        public string ClientActivityType;
        public string ClientActivityName;
        public UserStepStatus ClientActivityStatus;
        public string Message;
        public Exception InnerException;
    }

    [Designer("System.Activities.Core.Presentation.SequenceDesigner, System.Activities.Core.Presentation")]
    public class UserStepSequence : NativeActivity
    {
        // Defines whether or not step activities within the sequence may be executed out of order
        [RequiredArgument]
        public InArgument<bool> Ordered { get; set; }

        [RequiredArgument]
        public InArgument<string> BookmarkName { get; set; }

        [Browsable(false)]
        public Sequence InnerSequence { get; private set; }

        [Browsable(false)]
        public UserStepStatus Status { get; private set; }

        [Browsable(false)]
        public Collection<Activity> Activities
        {
            get { return InnerSequence.Activities; }
        }

        [Browsable(false)]
        public Collection<Variable> Variables
        {
            get { return InnerSequence.Variables; }
        }

        public UserStepSequence()
        {
            this.InnerSequence = new Sequence();
            _activitiesInitialized = false;
        }

        /// <summary>
        /// Event for notifying user step sequence completed.
        /// </summary>
        public event UserStepSequenceChangedEventHandler UserStepSequenceChanged;

        private void OnUserStepSequenceChanged(UserStepSequenceChangedEventArgs e)
        {
            if (UserStepSequenceChanged != null)
                UserStepSequenceChanged(this, e);
        }

        private string BuildStatusReport()
        {
            StringBuilder statusReport = new StringBuilder();
            statusReport.Append(
                string.Format(
                    "Activity:\t\t'{0}'\r\nBookmark:\t\t'{1}'\r\nOrdered:\t\t{2}\r\nStatus:\t\t{3}\r\n",
                    this.DisplayName,
                    this.BookmarkName.Expression,
                    Helper.ConvertArgumentToBool(this.Ordered),
                    this.Status.ToString()));

            this.InnerSequence.Activities.All(a =>
                                                  {
                                                      statusReport.Append(((UserStepActivity) a).GetStatusMessage() +
                                                                          Environment.NewLine);
                                                      return true;
                                                  });
            //for (int index = 0; index < this.InnerSequence.Activities.Count; index++)
            //{
            //    UserStepActivity userStep = this.InnerSequence.Activities[index] as UserStepActivity;
            //    if (userStep != null)
            //    {
            //        statusReport.Append(userStep.GetStatusMessage() + Environment.NewLine);
            //    }
            //}

            return statusReport.ToString();
        }

        private bool _activitiesInitialized = default(bool);
        private void InitializeActivities()
        {
            for (int index = 0; index < this.InnerSequence.Activities.Count; index++)
            {
                var userStep = this.InnerSequence.Activities[index] as UserStepActivity;
                if (userStep != null)
                {
                    userStep.Index = index;
                }
            }

            _activitiesInitialized = true;
        }

        private UserStepActivity _nextActivity = default(UserStepActivity);
        private UserStepActivity NextActivity
        {
            get
            {
                if (_nextActivity == default(UserStepActivity))
                {
                    this.SetNextActivity();
                }

                return _nextActivity;
            }
        }

        private bool SetNextActivity()
        {
            var nextActivity =
                this.InnerSequence.Activities.FirstOrDefault(
                    a => ((UserStepActivity) a).Status.IsNotSet(UserStepStatus.Completed) &&
                         ((UserStepActivity) a).Status.IsNotSet(UserStepStatus.CompletedWithErrors)) as UserStepActivity;

            if (nextActivity != null)
            {
                this._nextActivity = nextActivity;
                return true;
            }

            return false;
        }

        public bool UpdateActivityStatus(string bookmarkName, UserStepStatus newStatus, out string resultMessage)
        {
            bool updateResult = false;
            resultMessage = default(string);
            var userStep =
                this.InnerSequence.Activities.FirstOrDefault(
                    a => ((UserStepActivity) a).BookmarkName.Expression.ToString() == bookmarkName) as UserStepActivity;

            if (userStep != null)
            {
                updateResult = userStep.UpdateStatus(newStatus, out resultMessage);
                this.UpdateClientActivityStatus();
            }
            else
            {
                resultMessage = string.Format("Unrecognized activity sequence bookmark: '{0}'", bookmarkName);
            }

            return updateResult;
        }

        private void UpdateClientActivityStatus(UserStepStatus status = UserStepStatus.Unknown)
        {
            // if all completed == completed
            // if completed + completed with errors == completed with errors
            // if all initialized == initialized
            // else started
            switch (status)
            {
                case UserStepStatus.Unknown:
                    if (this.InnerSequence.Activities.All(a => ((UserStepActivity)a).Status.IsSet(UserStepStatus.Completed)))
                    {
                        this.Status = UserStepStatus.Completed;
                        OnUserStepSequenceChanged(this.BuildEventArgs(BuildStatusReport(),
                                                                      null));
                    }
                    else if (this.InnerSequence.Activities.All(a => (((UserStepActivity)a).Status.IsSet(UserStepStatus.Completed) ||
                        ((UserStepActivity)a).Status.IsSet(UserStepStatus.CompletedWithErrors))))
                    {
                        this.Status = UserStepStatus.CompletedWithErrors;
                    }
                    else if (this.InnerSequence.Activities.All(a => ((UserStepActivity)a).Status.IsSet(UserStepStatus.Initialized)))
                    {
                        this.Status = UserStepStatus.Initialized;
                    }
                    else
                    {
                        this.Status = UserStepStatus.Started;
                    } 
                    break;
                default:
                    this.Status = status;
                    break;
            }
        }

        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
            //metadata.AddImplementationChild(InnerSequence);
            foreach (Activity a in InnerSequence.Activities)
                metadata.AddImplementationChild(a);
        }

        protected override void Execute(NativeActivityContext context)
        {
            if (this._activitiesInitialized == false)
            {
                this.InitializeActivities();
            }

            context.CreateBookmark(BookmarkName.Expression.ToString(), OnResumeBookmark, BookmarkOptions.MultipleResume);
        }

        // NativeActivity derived activities that do asynchronous operations by calling 
        // one of the CreateBookmark overloads defined on System.Activities.NativeActivityContext 
        // must override the CanInduceIdle property and return true.
        protected override bool CanInduceIdle
        {
            get { return true; }
        }

        private UserStepSequenceChangedEventArgs BuildEventArgs(string message, Exception ex)
        {
            return new UserStepSequenceChangedEventArgs()
                       {
                           ClientActivityName = this.BookmarkName.Expression.ToString(),
                           ClientActivityType = this.DisplayName,
                           ClientActivityStatus = this.Status,
                           Message = message,
                           InnerException = ex
                       };
        }

        public void OnResumeBookmark(NativeActivityContext context, Bookmark bookmark, object obj)
        {
            // the root activity is resumed, and in turn targets a child activity to schedule
            var userStep = InnerSequence.Activities.FirstOrDefault(
                a => ((UserStepActivity) a).BookmarkName.Expression.ToString() == (string) obj) as UserStepActivity;

            if (userStep != null)
            {
                bool isValid = true;

                // if ordered, do not allow skipping ahead (but "rewind" is allowed if step is marked re-runnable)
                if (Helper.ConvertArgumentToBool(this.Ordered) == true)
                {
                    // is the requested activity index greater than last completed?
                    if (userStep != this.NextActivity)
                    {
                        if (userStep.Index > this.NextActivity.Index)
                        {
                            OnUserStepSequenceChanged(this.BuildEventArgs(
                                string.Format(
                                    "Activity Sequence Index: {0} requested, but Index: {1} expected (Ordered Sequence)",
                                    userStep.Index,
                                    this.NextActivity.Index), null));
                            isValid = false;
                        }
                    }
                }
                
                if ((userStep.Status.IsSet(UserStepStatus.Completed) || userStep.Status.IsSet(UserStepStatus.CompletedWithErrors)) &&
                        Helper.ConvertArgumentToBool(userStep.ReRunnable) == false)
                {
                    // for un-ordered sequences, any activity is eligible as long as it is re-runnable or not yet marked completed
                    OnUserStepSequenceChanged(this.BuildEventArgs(
                        string.Format(
                            "Activity Sequence Index: {0} requested, but sequence marked not re-runnable.",
                            userStep.Index,
                            this.NextActivity.Index), null));
                    isValid = false;
                }

                if (isValid == true)
                {
                    context.ScheduleActivity(userStep, OnCompleted, OnFaulted);
                }
            }
            else
            {
                OnUserStepSequenceChanged(this.BuildEventArgs(
                    string.Format("No Activity Sequence Found for Bookmark Name: '{0}'", obj), null));
            }
        }

        private void OnFaulted(NativeActivityFaultContext faultContext, Exception propagatedException, ActivityInstance propagatedFrom)
        {
            this.UpdateClientActivityStatus(UserStepStatus.Errored);
            OnUserStepSequenceChanged(this.BuildEventArgs(
                string.Format("Client Activity: '{0}' Faulted", this.BookmarkName.Expression.ToString()),
                propagatedException));
        }

        private void OnCompleted(NativeActivityContext context, ActivityInstance completedInstance)
        {
            this.SetNextActivity();
            this.UpdateClientActivityStatus();

            OnUserStepSequenceChanged(this.BuildEventArgs(((UserStepActivity)completedInstance.Activity).BookmarkNameValue,
                                              null));
        }
    }
}
